﻿/*
*   LightScript JavaScript framework, version 1.0.0
*   (c) 2008-2009 King
*   Email: kingkoft@126.com
*   Revision: 0
*   Usage: <srcipt src="http://a.b.c/Core.js" id="LSFramework" charset="utf-8" shortcut="LS" debug="true" traceContainerId="" culture="zh-CN" requestMode="xmlHttp|scriptHttp"></script>
*/ 

var LightScript = {};
LightScript.version = "1.0.0";
LightScript.appId = "LSFramework";
LightScript.appObject = document.getElementById(LightScript.appId);
LightScript.config = window.LightScript_Config || null;
window[(LightScript.config ? LightScript.config.shortcut : LightScript.appObject.getAttribute("shortcut")) || "LS"] = LightScript;

//Core, the core class only contains static methods.
(function(){
//# Private Members
var _classLoadCompletedHandlers = [];
forEach.Continue = 1;
forEach.Break = 2;

//# Constructions
createNamespace("LightScript");
var thisClass = createClass("LightScript.Core");

//# Public Members
//Initializes the config used by this class.
LightScript.enableDebug = getConfig("debug") == "true";
LightScript.traceContainerId = getConfig("traceContainerId");
LightScript.enableTrace = !!LightScript.traceContainerId;
LightScript.appRoot = getConfig("src");
/*if(LightScript.appRoot.search(/:\/\//) < 0)
{
    LightScript.appRoot = document.location.href.substring(0, document.location.href.lastIndexOf("/") + 1) + 
        (LightScript.appRoot.indexOf("/") == 0 ? LightScript.appRoot.substring(1) : LightScript.appRoot);
}*/
LightScript.appRoot = LightScript.appRoot.substring(0, LightScript.appRoot.lastIndexOf("/") + 1);

//# Private Methods
function getNamespace(namespaceString)
{
    var space = window;
    var spaces = namespaceString.split(".");
    for (var i = 0, len = spaces.length; i < len; i++) 
    {
        space = space[spaces[i]];
        if(!space) break;
    }

    return space;
}

function getClass(classString)
{
    var theClass;
    var segs = classString.split(".");
    if(segs.length > 1)
    {
        var className = segs.pop();
        var space = getNamespace(segs.join("."));
        if(space)
            theClass = space[className];
    }
    else
        theClass = window[classString];

    return theClass;
}

// Checks all the callback that depend on specific classses, if the classes are all loaded, then invoke the callback and remove this depending item from list.
function notifyClassLoadCompleted()
{
    var handlers = _classLoadCompletedHandlers;
    if(handlers._isUsing)   //Check whether it's locked
        return;

    handlers._isUsing = true; //Lock
    var item, ready;
    for (var i = handlers.length -1 ; i >= 0; i--) //Last added first invoked
    {
        //[classes, fn]
        ready = true;
        item = handlers[i];
        for (var j = 0, len2 = item[0].length; j < len2; j++)
        {
            if(!getClass(item[0][j]))
            {
                ready = false;
                break;
            }
        }
        if(ready)
        {
            item[1](); //The callback that depends on the classes in item[0]
            handlers.splice(i, 1);
        }
    }
    handlers._isUsing = false;   //Release
    if(handlers.length > 0)
        setTimeout(arguments.callee, 20);
}

//# Public Methods
thisClass.forEach = forEach;
function forEach(IList, elementDelegate)
{
    if(IList == null)
        return;

    var ret;
    for (var i = 0, len = IList.length; i < len; i++)
    {
        ret = elementDelegate(IList[i], i, IList, len);
        if (ret === forEach.Continue)
            continue;
        else if (ret === forEach.Break)
            break;
        else if (ret != undefined)
            return ret;
    }
}

thisClass.markAsAbstract = markAsAbstract;
function markAsAbstract(methodName)
{
    throw new LightScript.Exception(ReferenceError, arguments, LightScript.message.notImplemented, methodName);
}

thisClass.createNamespace = createNamespace;
function createNamespace(namespaceString)
{
    var space;
    if (namespaceString.length > 0)
    {
        space = window;
        var spaces = namespaceString.split(".");
        for (var i = 0, len = spaces.length; i < len; i++)
        {
            if (!(space[spaces[i]] instanceof Object)) space[spaces[i]] = {};
            space = space[spaces[i]];
        }
    }

    return space;
}

thisClass.createClass = createClass;
function createClass(classDeclaring, constructor)
{
    //Man : Human, Male, IAnimal, IDispose
    classDeclaring = classDeclaring.replace(/ /g, ""); 
    if(!constructor) constructor = function(){};
    var temp = classDeclaring.split(":");
    var namespace, className, fullClassName = temp[0];
    var segs = fullClassName.split(".");
    if(segs.length > 1)
    {
        className = segs.pop();
        namespace = getNamespace(segs.join("."));
    }
    else
    {
        namespace = window;
        className = fullClassName;
    }

    if(namespace[className])
        throw new LightScript.Exception(ReferenceError, arguments, LightScript.message.classIsDefined, className);

    var theClass = namespace[className] = constructor;

    if(temp.length > 1) //Has base class
    {
        var baseClass, baseClasses = temp[1];
        temp = baseClasses.split(",");   //Base classes
        for (var i = 0, len = temp.length; i < len; i++) 
        {
            baseClass = getClass(temp[i]);
            if(!baseClass)
                throw new LightScript.Exception(ReferenceError, arguments, LightScript.message.classNotDefined, temp[i]);
            for (var p in baseClass.prototype)
                theClass.prototype[p] = baseClass.prototype[p]; //Inherits instance members and methods

            copyProperties(baseClass, theClass); //Inherits static members and methods
        }
        theClass._baseClasses = baseClasses;   //Set base classes
    }
    theClass.prototype._type = fullClassName;  //Set instance type

    return theClass;
}

thisClass.base = base;
function base(instance, baseClass, baseMethodName, args)
{
    //base(this, Human,  ["", ""]);   base(this, Human, "walk",  ["", ""]);
    var ret;
    if(!args)
    {
        if(baseMethodName)
        {
            if(typeof(baseMethodName) != "string")   //Parameters of constructor
            {
                args = baseMethodName;
                baseMethodName = "constructor";
            }
        }
        else
            baseMethodName = "constructor";
    }

    if(baseClass.prototype[baseMethodName])
    {
        ret = args ? baseClass.prototype[baseMethodName].apply(instance, args) : 
            baseClass.prototype[baseMethodName].call(instance);
    }
    else //Find on top level
    {
        var baseClasses = getBaseClasses(baseClass);
        var len = baseClasses.length;
        if(len == 0)
            throw new LightScript.Exception(ReferenceError, arguments, LightScript.message.methodNotExisted, baseMethodName);
        for (var i = 0; i < len; i++)
        {
            baseClass = baseClasses[i];
            ret = base(instance, baseClass, baseMethodName, args);
        }
    }

    return ret;
}

thisClass.getTypeName = getTypeName;
function getTypeName(instance)
{
    return instance._type;
}

thisClass.getType = getType;
function getType(instance)
{
    return getClass(instance._type);
}

thisClass.isType = isType;
function isType(instanceOrClass, classOrClassName)
{
    var ret = false;
    var type = typeof(classOrClassName) == "string" ? getClass(classOrClassName) : classOrClassName;
    var theClass = getClass(instanceOrClass._type || instanceOrClass.prototype._type);
    ret = theClass == type;
    if(!ret)
    {
        var baseClasses = getBaseClasses(theClass);
        for (var i = 0, len = baseClasses.length; i < len; i++)
        {
            if(baseClasses[i] == type)
                ret = true;
            else
                ret = isType(baseClasses[i], type);
            if(ret)
                break;
        }
    }

    return ret;
}

thisClass.getBaseClasses = getBaseClasses;
function getBaseClasses(theClass)
{
    var classes = [];
    if(theClass._baseClasses)
    {
        var classNames = theClass._baseClasses.split(",");
        for (var i = 0, len = classNames.length; i < len; i++)
            classes.push(getClass(classNames[i]));
    }

    return classes;
}

thisClass.copyProperties = copyProperties;
function copyProperties(source, target, recursive)
{
    recursive = !!recursive;
    for (var p in source)
    {
        if(source[p].clone)   //If source implements deep copy, then use it.
            target[p] = source[p].clone();
        else
            target[p] = source[p];

        if(recursive && typeof(source[p]) == "object")
            copyProperties(source[p], target[p], recursive);
    }
}

//extraParams<Object>: {type:"text/javascript", charset: "utf-8"} (optional)
thisClass.loadScript = loadScript;
function loadScript(url, callback, extraParams)
{
    if(extraParams && extraParams.onlyLoadOnce)
    {
        if(!arguments.callee._loadedUrls)
            arguments.callee._loadedUrls = {};
        if(arguments.callee._loadedUrls[url])
            return;
        arguments.callee._loadedUrls[url] = (new Date()).getTime();
    }
    var container = document.getElementsByTagName("head");
    container = container && container.length > 0 ?  container[0] : document.body;
    var e = document.createElement("script");
    e.type = "text/javascript";
    e.charset = "utf-8";
    e.src = url;
    if(extraParams)
    {
        if(extraParams.type)
            e.type = extraParams.type;
        if(extraParams.charset)
            e.charset = extraParams.charset;
    }
    container.appendChild(e);
    if (typeof (callback) == "function")
    {
        e.onload = e.onreadystatechange = function()
        {
            if (e.readyState && e.readyState != "loaded" && e.readyState != "complete") return;
            e.onreadystatechange = e.onload = null;
            callback();
        };
    }
}

//e.g: using("classA", "classB", "classC", function(){});
thisClass.using = using;
function using()
{
    var classes = [];
    var fn;
    forEach(arguments, function(e, i, ary, len)
    {
        if(typeof(e) == "string")
            classes.push(e);
        else if(i+1== len)
            fn = e;
    });

    var theClass, name, charset, path, url, fullUrl, segs, files = [];
    var isUrl, isInnerClass;
    for (var i = 0, len = classes.length; i < len; i++)
    {
        charset = classes[i].split("$");
        if(charset.length > 1)  //e.g: demo@LightScript._Additions.Demo$utf-8
        {
            classes[i] = charset[0];
            charset = charset[1];
        }
        else
            charset = "utf-8";

        name = classes[i].split("@");
        if(name.length > 1)     //e.g: MyClassA@LightScript._Additions.MyClass
        {
            path = name[1].replace(/^\s+/g, "");
            classes[i] = name = name[0].replace(/\s/g, "");
        }
        else
            name = path = name[0] //e.g: LightScript.Array
        theClass = getClass(name);
        if(!theClass)
        {
            isUrl = path.search(/:\/\//) >= 0;
            if(isUrl) //Outer url, e.g: http://a.b.c/x.js
                fullUrl = path;
            else
            {
                segs = path.split(".");
                isInnerClass = segs[0] == "LightScript" || segs[0] == getConfig("shortcut") || segs[0] == "LS";
                url = [];

                if(isInnerClass) //e.g: LightScript.Dom
                {
                    url.push(LightScript.appRoot);
                    segs.shift();   //Remove the top namespace
                }
                else if(location.protocol != "file:") //e.g: MyNamespace.MyClass
                    url.push("/");  //Outer classes, relatives to root directory.

                if(segs.length > 1) //Directory
                {
                    for (var j = 0, len2 = segs.length - 1; j < len2; j++)
                    {
                        url.push(segs[j]);
                        url.push("/");
                    }
                    url.push(segs.pop());
                }
                else
                    url.push(segs.join(""));
                url.push(".js");
                fullUrl = url.join("");
            }
            files.push([fullUrl, charset]);
        }
    }

    var len = files.length;
    if(len == 0) //All classes are already loaded
    {
        if(fn) fn();
    }
    else
    {
        var fx = function()
        {
            if(!fn)
            {
                notifyClassLoadCompleted();
                return;
            }
            if(!fn._usedClasses)
                fn._usedClasses = 1;
            if(fn._usedClasses == arguments.callee._usingClasses)  //After all the using files are loaded
                notifyClassLoadCompleted();
            else
                fn._usedClasses++;
        };
        fx._usingClasses = files.length;
        if(fn)
            _classLoadCompletedHandlers.push([classes, fn]);   //Add the callback to depending list
        for (var i = 0, len = files.length; i < len; i++)
            loadScript(files[i][0], fx, {onlyLoadOnce: true, charset: files[i][1]});
    }
}

//Generates a new id.
thisClass.newId = newId;
function newId()
{
    return Math.abs(((new Date()).getTime()>>12)*1000 + Math.floor(Math.random()*1000));
}

thisClass.getConfig = getConfig;
function getConfig(name)
{
    return LightScript.config ? LightScript.config[name] : LightScript.appObject.getAttribute(name);
}

thisClass.addShortcut = addShortcut;
function addShortcut(name, reference)
{
    trace([name, LightScript[name]]);
    if(LightScript[name] && LightScript[name] != reference)
        throw new LightScript.Exception(ReferenceError, arguments, LightScript.message.shortcutDuplicated, name);
    LightScript[name] = reference;
}

thisClass.trace = trace;
function trace(msg)
{
    if(LightScript.enableTrace)
    {
        var e = document.getElementById(LightScript.traceContainerId);
        if(!e)
            return;
        if(arguments.length > 0)
            e.value += msg + "\n";
        else
            e.value = "";
    }
}

thisClass.message = 
{
    runtimeError: "runtimeError.",
    notImplemented: "The method or operation is not implemented.",
    methodNotExisted: "The method is not existed.",
    classIsDefined: "The class is already defined.",
    classNotDefined: "The class is not defined.",
    shortcutDuplicated: "The shortcut name is duplicated.",
    notSupportXmlHttp: "XmlHttp is not supported by your system.",
    htmlHttpError: "Error occurs in HtmlHttp.",
    eof: null
}

//# Shortcuts
addShortcut("addShortcut", thisClass.addShortcut);
LightScript.addShortcut("forEach", thisClass.forEach);
LightScript.addShortcut("using", thisClass.using);
LightScript.addShortcut("trace", thisClass.trace);
LightScript.addShortcut("message", thisClass.message);
LightScript.addShortcut("g", function(){return document.getElementById.apply ? document.getElementById.apply(document, arguments) : document.getElementById(arguments[0]); });
LightScript.addShortcut("gs", function() { return document.getElementsByTagName.apply ? document.getElementsByTagName.apply(document, arguments) : document.getElementsByTagName(arguments[0]); });
})();


//Cache, only contains static methods. 
(function(){
//# Private Members
var _content = {};

//# Constructions
LightScript.Core.createNamespace("LightScript");
var thisClass = LightScript.Core.createClass("LightScript.Cache");

//# Public Members

//# Private Methods

//# Public Methods
thisClass.get = get;
function get(key)
{
    return _content[key + ""];
}

thisClass.getAll = getAll;
function getAll()
{
    return _content;
}

thisClass.set = set;
thisClass.add = set;
function set(key, value)
{
    _content[key + ""] = value;
}

thisClass.remove = remove;
function remove(key)
{
    delete _content[key + ""];
}

thisClass.clear = clear;
function clear(prefix)
{
    if(prefix == undefined)
    {
        delete _content;
        _content = {};
    }
    else
    {
        for (var p in _content) 
        {
            if(typeof(p) == "string" && p.indexOf(prefix) == 0)
                delete content[p];
        }
    }
}

//# Shortcuts

})();


//ObjectList
(function(){
//# Private Members
var prefix = "obj_";

//# Constructions
LightScript.Core.createNamespace("LightScript");
var thisClass = LightScript.Core.createClass("LightScript.ObjectList: LightScript.Cache");

//# Public Methods
thisClass.add = add;
function add(objectId, value)
{
    var key = prefix + objectId;
    LightScript.Cache.add(key, value);
}

thisClass.get = get;
function get(objectId)
{
    var key = prefix + objectId;
    return LightScript.Cache.get(key);
}

thisClass.remove = remove;
function remove(objectId)
{
    var key = prefix + objectId;
    LightScript.Cache.remove(key);
}

thisClass.clear = clear;
function clear(objectId)
{
    LightScript.Cache.clear(prefix);
}

//# Shortcuts
LightScript.addShortcut("find", thisClass.get);

})();


//EventList
(function(){
//# Private Members
var prefix = "evt_";

//# Constructions
LightScript.Core.createNamespace("LightScript");
var thisClass = LightScript.Core.createClass("LightScript.EventList: LightScript.Cache");

//# Public Methods
// Sets/Appends an handler to an event.
//isAppend<Boolean>: optional.
thisClass.add = add;
function add(objectId, eventName, handler, isAppend)
{
    var key = prefix + objectId + "_" + eventName;
    if(isAppend)
    {
        var binded = LightScript.Cache.get(key);
        if(binded instanceof Array)
            binded.push(handler);
        else if(binded)
            LightScript.Cache.set(key, [binded, handler]);
        else
            LightScript.Cache.set(key, [handler]);
    }
    else
        LightScript.Cache.set(key, handler);
}

// Gets the handler of an element's event. This method is used to bind handler to the dom event.
thisClass.get = get;
function get(objectId, eventName)
{
    return function(ev)
    {
        if(!ev)
            ev = window.event;
        raise(objectId, eventName, ev.srcElement || ev.target, ev);   //Last  two: sender, args
    };
}

// Removes an event handler/all handlers.
//handler<Function>: optional.
thisClass.remove = remove;
function remove(objectId, eventName, handler)
{
    var key = prefix + objectId + "_" + eventName;
    if(handler)
    {
        var binded = LightScript.Cache.get(key);
        if(binded instanceof Array)
        {
            for (var i = 0, len = binded.length; i < len; i++)
            {
                if(binded[i] == handler)
                {
                    binded.splice(i, 1);
                    break;
                }
            }
            if(binded.length == 0)
                LightScript.Cache.remove(key);
        }
        else if(binded == handler)
            LightScript.Cache.remove(key);
    }
    else
        LightScript.Cache.remove(key);
}

// Raises an event.
thisClass.raise = raise;
function raise(objectId, eventName, sender, args)
{
    var key = prefix + objectId + "_" + eventName;
    var handler = LightScript.Cache.get(key);
    if(handler instanceof Array)
    {
        for (var i = 0, len = handler.length; i < len; i++)
            handler[i](sender, args);
    }
    else if(handler)
        handler(sender, args);
}

thisClass.clear = clear;
function clear()
{
    LightScript.Cache.clear(prefix);
}

// The following methods(attachEvent/detachEvent) are not recommended.
thisClass.attachEvent = attachEvent;
function attachEvent(element, eventName, handler)
{
    if (element.attachEvent)
    {
        var key = eventName + handler;
        if(element["_" + key] != handler)
        {
            element["_" + key] = handler;
            element[key] = function() { element["_" + key](window.event); }
            element.attachEvent("on" + eventName, element[key]);
        }
    }
    else
        element.addEventListener(eventName, handler, false);
}

thisClass.detachEvent = detachEvent;
function detachEvent(element, eventName, handler)
{
    if (element.detachEvent)
    {
        var key = eventName + handler;
        element.detachEvent("on" + eventName, element[key]);
        element[key] = null;
        element["_" + key] = null;
    }
    else
        element.removeEventListener(eventName, handler, false);
}

//# Shortcuts

})();


//Exception
(function(){
//# Private Members
var _content = [];
//Initializes the config used by this class.
var maxItems = LightScript.Core.getConfig("maxExceptionsCount") || 100;
var throwGenericError = LightScript.Core.getConfig("throwGenericError") !== "false";
var functionPattern = /function\s+(.*?)\s*\(/;

//# Constructions
//type<Function>: EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError
LightScript.Core.createNamespace("LightScript");
var thisClass = LightScript.Core.createClass("LightScript.Exception: Error", function(type, stack, message, description, innerException, shouldSave)
{
    if(!type || typeof(type) != "function")
        type = Error;
    this.name = type.name || "Error";
    this.message = message;
    this.description = description;
    this.innerException = innerException || null;
    this.ticks = (new Date()).getTime();

    if(stack && stack.callee)
    {
        if(!this.description)
            this.description = "";
        this.description += " Stack: " + stack.callee.toString().match(functionPattern)[1] + 
            " Args: " + Array.prototype.join.call(stack, ";");
    }
    if(shouldSave !== false)
        _content.unshift(this);

    while(_content.length > maxItems)
        _content.pop();

    return throwGenericError ? new Error(this.name + "\n" + message + "\n" + description) :
        new type(this.name + " " + this.message + "\n" + this.description);
});

//# Public Members
thisClass.prototype.innerException = null;
thisClass.prototype.ticks = null;

//# Private Methods

//# Public Methods
thisClass.getAll = getAll;
function getAll()
{
    return _content;
}

thisClass.clear = clear;
function clear()
{
    _content = [];
}

//# Shortcuts

})();


LightScript.EventList.attachEvent(window, "unload", function() { LightScript = null; });